/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_speaker.cpp - speaker and ambience entities and editor managemnt
//

// FIXME: SG_LinkSpeaker & SG_SpeakerEditorSaveCallback


#include "sg_local.h"


enum speakerFlags_t {
	SPEAKER_GLOBAL					= BIT(0),
	SPEAKER_LOOPING					= BIT(1),
	SPEAKER_TRIGGERED				= BIT(2),
	SPEAKER_ACTIVATOR				= BIT(3)
};


/*
 ==============================================================================

 SPEAKER

 ==============================================================================
*/


/*
 ==================
 SG_Speaker_Think
 ==================
*/
static void SG_Speaker_Think (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Play the looping sound if needed
	if (self->spawnFlags & SPEAKER_LOOPING){
		speaker->sound = self->soundShader;
		speaker->soundTime = level.time;

		return;
	}

	// Play the sound
	if (!self->soundShader)
		return;

	self->AddEvent(EV_PLAYSOUND, (SND_CHANNEL_MISC << 12) | self->soundShader);

	// Set the time for the next auto-trigger
	self->nextThinkTime = level.time + SEC2MS(self->wait + self->random * game.random.CRandFloat());
}

/*
 ==================
 SG_Speaker_Enabled
 ==================
*/
static void SG_Speaker_Enabled (etEntity *self){

	// Automatically start it if needed
	if (!(self->spawnFlags & SPEAKER_TRIGGERED)){
		if (self->wait){
			self->nextThinkTime = level.time + SEC2MS(self->wait + self->random * game.random.CRandFloat());
			return;
		}

		self->Trigger(self, NULL, self);
	}
}

/*
 ==================
 SG_Speaker_Disabled
 ==================
*/
static void SG_Speaker_Disabled (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Disable auto-trigger
	self->nextThinkTime = 0;

	// Stop the looping sound if needed
	if (self->spawnFlags & SPEAKER_LOOPING){
		speaker->sound = 0;
		speaker->soundTime = level.time;

		return;
	}

	// Stop the sound
	if (!self->soundShader)
		return;

	self->AddEvent(EV_STOPSOUND, (SND_CHANNEL_MISC << 12) | self->soundShader);
}

/*
 ==================
 SG_Speaker_Trigger
 ==================
*/
static void SG_Speaker_Trigger (etEntity *self, etEntity *other, etEntity *activator){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	if (!self->IsEnabled())
		return;

	// Disable auto-trigger
	self->nextThinkTime = 0;

	// Toggle looping sound on/off if needed
	if (self->spawnFlags & SPEAKER_LOOPING){
		if (speaker->sound)
			speaker->sound = 0;
		else
			speaker->sound = self->soundShader;

		speaker->soundTime = level.time;

		return;
	}

	// Play the sound
	if (!self->soundShader)
		return;

	if (self->spawnFlags & SPEAKER_ACTIVATOR){
		if (!activator || !activator->client)
			return;

		activator->AddEvent(EV_PLAYSOUND, (SND_CHANNEL_MISC << 12) | self->soundShader);

		return;
	}

	self->AddEvent(EV_PLAYSOUND, (SND_CHANNEL_MISC << 12) | self->soundShader);
}

/*
 ==================
 SG_Speaker_Spawn
 ==================
*/
void SG_Speaker_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;
	const char	*name;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the flags
	if (self->spawnDict.GetBool("global"))
		self->spawnFlags |= SPEAKER_GLOBAL;
	if (self->spawnDict.GetBool("looping"))
		self->spawnFlags |= SPEAKER_LOOPING;
	if (self->spawnDict.GetBool("triggered"))
		self->spawnFlags |= SPEAKER_TRIGGERED;
	if (self->spawnDict.GetBool("activator"))
		self->spawnFlags |= SPEAKER_ACTIVATOR;

	// Get the auto-trigger parameters
	self->wait = self->spawnDict.GetFloat("wait");
	self->wait = Max(self->wait, 0.0f);

	self->random = self->spawnDict.GetFloat("random");
	self->random = Max(self->random, 0.0f);

	if (self->wait == 0.0f)
		self->random = 0.0f;
	else {
		self->wait = Max(self->wait, 0.001f);
		self->random = Min(self->random, self->wait - 0.001f);
	}

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	name = self->spawnDict.GetString("soundShader");
	self->soundShader = SG_SoundShaderIndex(name);

	// Finish setting up
	if (self->spawnFlags & SPEAKER_GLOBAL)
		self->flags |= FL_BROADCAST;

	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();

	// Set the callback functions
	self->Think = SG_Speaker_Think;
	self->Enabled = SG_Speaker_Enabled;
	self->Disabled = SG_Speaker_Disabled;
	self->Trigger = SG_Speaker_Trigger;

	// Automatically start it if needed
	if (!self->IsEnabled())
		return;

	if (!(self->spawnFlags & SPEAKER_TRIGGERED)){
		if (self->wait){
			self->nextThinkTime = level.time + SEC2MS(self->wait + self->random * game.random.CRandFloat());
			return;
		}

		self->Trigger(self, NULL, self);
	}
}


/*
 ==============================================================================

 AMBIENT_SUCK_WIND

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientSuckWind_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/suck");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_DRONE

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientDrone_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/drone");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_FLOURO_BUZZ

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientFlouroBuzz_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/buzz");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_DRIP

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientDrip_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/drip");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_COMP_HUM

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientCompHum_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/hum");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_THUNDER

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientThunder_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/thunder");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_LIGHT_BUZZ

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientLightBuzz_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/lightBuzz");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_SWAMP1

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientSwamp1_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/swamp1");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 AMBIENT_SWAMP2

 ==============================================================================
*/


/*
 ==================

 ==================
*/
void SG_AmbientSwamp2_Spawn (etEntity *self){

	speaker_t	*speaker = (speaker_t *)self->state.buffer;

	// Parse the spawn parameters that will be transmitted to clients
	self->ParseSpeakerState();

	// Get the audible range
	self->audibleRange = self->spawnDict.GetFloat("audibleRange", "2500");
	self->audibleRange = Max(self->audibleRange, 0.0f);

	// Get the sound shader
	self->soundShader = SG_SoundShaderIndex("ambience/swamp2");

	// Finish setting up
	if (self->soundShader)
		self->flags |= FL_AUDIBLE;

	// Enable physics
	self->EnablePhysics();
}


/*
 ==============================================================================

 INTEGRATED SPEAKER EDITOR

 ==============================================================================
*/

struct speakerParms_t {
	char					name[MAX_ENTITY_NAME_LENGTH];

	char					bind[MAX_ENTITY_NAME_LENGTH];
	bool					bindOrientated;

	// Transformation matrix
	glqVec3					origin;
	glqVec3					direction;

	// Server-side parameters
	int						flags;

	float					wait;
	float					random;

	float					audibleRange;

	// Sound shader
	char					soundShader[MAX_PATH_LENGTH];
	float					soundParms[MAX_SOUND_PARMS];
};

struct speakerEditor_t {
	bool					active;

	etEntity *				focusSpeaker;
	etEntity *				editSpeaker;
};

static speakerEditor_t		sg_speakerEditor;

static void *				SG_CreateSpeakerEditorWindow ();
static void					SG_DestroySpeakerEditorWindow ();

static void					SG_EditSpeakerParameters (int index, speakerParms_t *parms);


/*
 ==================
 SG_LinkSpeaker
 ==================
*/
static void SG_LinkSpeaker (etEntity *entity){

	speaker_t	*speaker = (speaker_t *)entity->state.buffer;

	if (entity->spawnFlags & SPEAKER_GLOBAL)
		entity->flags |= FL_BROADCAST;
	else
		entity->flags &= ~FL_BROADCAST;

	if (entity->soundShader)
		entity->flags |= FL_AUDIBLE;
	else
		entity->flags &= ~FL_AUDIBLE;

	entity->SetOrigin(entity->currentOrigin);
	entity->SetAngles(entity->currentAngles);

	// Enable physics
	entity->EnablePhysics();

	// Set the callback functions
	entity->Think = SG_Speaker_Think;
	entity->Enabled = SG_Speaker_Enabled;
	entity->Disabled = SG_Speaker_Disabled;
	entity->Trigger = SG_Speaker_Trigger;

	// Enable the entity
	entity->Enable();

	// Automatically start it if needed
	if (entity->spawnFlags & SPEAKER_TRIGGERED){
		entity->nextThinkTime = 0;
		return;
	}

	if (entity->wait){
		entity->nextThinkTime = level.time + SEC2MS(entity->wait + entity->random * game.random.CRandFloat());
		return;
	}

	entity->Trigger(entity, NULL, entity);
}

/*
 ==================
 SG_UpdateSpeakerSpawnDict
 ==================
*/
static void SG_UpdateSpeakerSpawnDict (etEntity *entity){

	speaker_t	*speaker = (speaker_t *)entity->state.buffer;

	// Clear the editable key/value pairs
	entity->spawnDict.RemoveKeyValue("bind");
	entity->spawnDict.RemoveKeyValue("bindOrientated");
	entity->spawnDict.RemoveKeyValue("angle");
	entity->spawnDict.RemoveKeyValue("angles");
	entity->spawnDict.RemoveKeyValue("rotation");
	entity->spawnDict.RemoveKeyValue("direction");
	entity->spawnDict.RemoveKeyValue("volume");
	entity->spawnDict.RemoveKeyValue("pitch");
	entity->spawnDict.RemoveKeyValue("dryFilter");
	entity->spawnDict.RemoveKeyValue("wetFilter");
	entity->spawnDict.RemoveKeyValue("minDistance");
	entity->spawnDict.RemoveKeyValue("maxDistance");
	entity->spawnDict.RemoveKeyValue("global");
	entity->spawnDict.RemoveKeyValue("looping");
	entity->spawnDict.RemoveKeyValue("triggered");
	entity->spawnDict.RemoveKeyValue("activator");
	entity->spawnDict.RemoveKeyValue("wait");
	entity->spawnDict.RemoveKeyValue("random");
	entity->spawnDict.RemoveKeyValue("audibleRange");
	entity->spawnDict.RemoveKeyValue("soundShader");
	entity->spawnDict.RemoveKeyValue("soundParm0");
	entity->spawnDict.RemoveKeyValue("soundParm1");
	entity->spawnDict.RemoveKeyValue("soundParm2");
	entity->spawnDict.RemoveKeyValue("soundParm3");
	entity->spawnDict.RemoveKeyValue("soundParm4");
	entity->spawnDict.RemoveKeyValue("soundParm5");
	entity->spawnDict.RemoveKeyValue("soundParm6");
	entity->spawnDict.RemoveKeyValue("soundParm7");

	// Set the editable key/value pairs
	entity->spawnDict.SetString("name", entity->name);
	entity->spawnDict.SetString("className", entity->className);

	if (entity->IsBound()){
		entity->spawnDict.SetString("bind", entity->GetBindMaster()->name);

		if (entity->GetBindOrientated())
			entity->spawnDict.SetBool("bindOrientated", true);
	}

	entity->spawnDict.SetVec3("origin", entity->currentOrigin);

	if (!entity->currentAngles.IsCleared()){
		if (entity->currentAngles.pitch == 0.0f && entity->currentAngles.roll == 0.0f)
			entity->spawnDict.SetFloat("angle", entity->currentAngles.yaw);
		else
			entity->spawnDict.SetMat3("rotation", entity->currentAxis);
	}

	if (!speaker->direction.IsCleared())
		entity->spawnDict.SetVec3("direction", speaker->direction);

	if (speaker->soundParms[SOUNDPARM_VOLUME] != 1.0f)
		entity->spawnDict.SetFloat("volume", speaker->soundParms[SOUNDPARM_VOLUME]);

	if (speaker->soundParms[SOUNDPARM_PITCH] != 1.0f)
		entity->spawnDict.SetFloat("pitch", speaker->soundParms[SOUNDPARM_PITCH]);

	if (speaker->soundParms[SOUNDPARM_DRYFILTER] != 1.0f || speaker->soundParms[SOUNDPARM_DRYFILTERHF] != 1.0f)
		entity->spawnDict.SetVec2("dryFilter", glqVec2(speaker->soundParms[SOUNDPARM_DRYFILTER], speaker->soundParms[SOUNDPARM_DRYFILTERHF]));

	if (speaker->soundParms[SOUNDPARM_WETFILTER] != 1.0f || speaker->soundParms[SOUNDPARM_WETFILTERHF] != 1.0f)
		entity->spawnDict.SetVec2("wetFilter", glqVec2(speaker->soundParms[SOUNDPARM_WETFILTER], speaker->soundParms[SOUNDPARM_WETFILTERHF]));

	if (speaker->soundParms[SOUNDPARM_MINDISTANCE] || speaker->soundParms[SOUNDPARM_MAXDISTANCE]){
		entity->spawnDict.SetFloat("minDistance", speaker->soundParms[SOUNDPARM_MINDISTANCE]);
		entity->spawnDict.SetFloat("maxDistance", speaker->soundParms[SOUNDPARM_MAXDISTANCE]);
	}

	if (entity->spawnFlags & SPEAKER_GLOBAL)
		entity->spawnDict.SetBool("global", true);

	if (entity->spawnFlags & SPEAKER_LOOPING)
		entity->spawnDict.SetBool("looping", true);

	if (entity->spawnFlags & SPEAKER_TRIGGERED)
		entity->spawnDict.SetBool("triggered", true);

	if (entity->spawnFlags & SPEAKER_ACTIVATOR)
		entity->spawnDict.SetBool("activator", true);

	if (entity->wait)
		entity->spawnDict.SetFloat("wait", entity->wait);

	if (entity->random)
		entity->spawnDict.SetFloat("random", entity->random);

	if (entity->audibleRange != 2500.0f)
		entity->spawnDict.SetFloat("audibleRange", entity->audibleRange);

	if (entity->soundShader)
		entity->spawnDict.SetString("soundShader", level.configStrings[CS_SOUND_SHADERS + entity->soundShader]);
}

/*
 ==================
 SG_SpeakerEditorUpdateCallback
 ==================
*/
static void SG_SpeakerEditorUpdateCallback (int index, speakerParms_t *parms){

	etEntity	*entity;
	speaker_t	*speaker;

	if (!sg_speakerEditor.active)
		return;			// Not active

	if (index < MAX_CLIENTS || index >= level.numEntities)
		return;			// Out of range

	entity = &level.entities[index];

	if (!entity->inUse || entity->state.type != ET_SPEAKER)
		return;			// Unused or invalid entity

	speaker = (speaker_t *)entity->state.buffer;

	// Copy the parameters and clamp as needed
	entity->SetName(SG_ValidateEntityName(parms->name, "speaker", index));

	entity->Bind(SG_FindEntity(parms->bind), parms->bindOrientated);

	entity->spawnFlags = parms->flags;
	entity->currentOrigin = parms->origin;
	entity->wait = Max(parms->wait, 0.0f);
	entity->random = (entity->wait == 0.0f) ? 0.0f : Max(parms->random, entity->wait - 0.001f);
	entity->audibleRange = Max(parms->audibleRange, 0.0f);
	entity->soundShader = SG_SoundShaderIndex(parms->soundShader);

	speaker->direction = parms->direction;
	speaker->sound = entity->soundShader;
	speaker->soundTime = level.time;
	speaker->soundParms[0] = glqMath::ClampFloat(parms->soundParms[0], 0.0f, 1.0f);
	speaker->soundParms[1] = glqMath::ClampFloat(parms->soundParms[1], 0.1f, 10.0f);
	speaker->soundParms[2] = glqMath::ClampFloat(parms->soundParms[2], 0.0f, 1.0f);
	speaker->soundParms[3] = glqMath::ClampFloat(parms->soundParms[3], 0.0f, 1.0f);
	speaker->soundParms[4] = glqMath::ClampFloat(parms->soundParms[4], 0.0f, 1.0f);
	speaker->soundParms[5] = glqMath::ClampFloat(parms->soundParms[5], 0.0f, 1.0f);
	speaker->soundParms[6] = Max(parms->soundParms[6], 0.0f);
	speaker->soundParms[7] = Max(parms->soundParms[7], 0.0f);

	// Renormalize
	speaker->direction.Normalize();

	// Update the entity dictionary
	SG_UpdateSpeakerSpawnDict(entity);

	// Link the entity
	SG_LinkSpeaker(entity);
}

/*
 ==================
 SG_SpeakerEditorRemoveCallback
 ==================
*/
static void SG_SpeakerEditorRemoveCallback (int index){

	etEntity	*entity;

	if (!sg_speakerEditor.active)
		return;			// Not active

	if (index < MAX_CLIENTS || index >= level.numEntities)
		return;			// Out of range

	entity = &level.entities[index];

	if (!entity->inUse || entity->state.type != ET_SPEAKER)
		return;			// Unused or invalid entity

	// Free the entity
	SG_FreeEntity(entity);

	// Force a reset
	sg_speakerEditor.focusSpeaker = NULL;
	sg_speakerEditor.editSpeaker = NULL;
}

/*
 ==================
 SG_SpeakerEditorSaveCallback
 ==================
*/
static void SG_SpeakerEditorSaveCallback (){

	etEntFile	entFile;
	char		name[MAX_PATH_LENGTH];
	etEntity	*entity;
	int			count;
	int			i;

	if (!sg_speakerEditor.active)
		return;			// Not active

	// Write or update the entity file
	Str_SPrintf(name, sizeof(name), "maps/%s.ent2", level.name);

	if (!glqFileSystem->FileExists(name)){
		// Add the new speaker entities
		count = 0;

		for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
			if (!entity->inUse || entity->state.type != ET_SPEAKER)
				continue;

			entFile.AddEntity(entity->spawnDict);

			count++;
		}

		// Write it out
		if (entFile.Write(name))
			G_Printf("Wrote %s with %i speakers\n", name, count);

		return;
	}

	if (entFile.Parse(name)){
		// Remove all the existing speaker entities
		entFile.RemoveEntities("speaker");

		// Add the new speaker entities
		count = 0;

		for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
			if (!entity->inUse || entity->state.type != ET_SPEAKER)
				continue;

			entFile.AddEntity(entity->spawnDict);

			count++;
		}

		// Write it out
		if (entFile.Write(name))
			G_Printf("Wrote %s with %i speakers\n", name, count);
	}
}

/*
 ==================
 SG_SpeakerEditorCloseCallback
 ==================
*/
static void SG_SpeakerEditorCloseCallback (){

	if (!sg_speakerEditor.active)
		return;			// Not active

	sg_speakerEditor.active = false;

	sg_speakerEditor.focusSpeaker = NULL;
	sg_speakerEditor.editSpeaker = NULL;

	// Close the editor
	glqCommon->CloseEditor();
}

/*
 ==================
 SG_SpeakerEditorMouseEvent
 ==================
*/
static bool SG_SpeakerEditorMouseEvent (){

	etEntity		*entity;
	speaker_t		*speaker;
	speakerParms_t	parms;

	if (!sg_speakerEditor.active)
		return false;	// Not active

	if (!sg_speakerEditor.focusSpeaker)
		return false;	// No speaker has focus

	// Edit the speaker that has focus
	entity = sg_speakerEditor.editSpeaker = sg_speakerEditor.focusSpeaker;

	speaker = (speaker_t *)entity->state.buffer;

	Str_Copy(parms.name, entity->name, sizeof(parms.name));

	if (!entity->IsBound()){
		Str_Copy(parms.bind, "", sizeof(parms.bind));

		parms.bindOrientated = false;
	}
	else {
		Str_Copy(parms.bind, entity->GetBindMaster()->name, sizeof(parms.bind));

		parms.bindOrientated = entity->GetBindOrientated();
	}

	parms.origin = entity->GetOrigin();
	parms.direction = speaker->direction;
	parms.flags = entity->spawnFlags;
	parms.wait = entity->wait;
	parms.random = entity->random;
	parms.audibleRange = entity->audibleRange;

	if (entity->soundShader == 0)
		Str_Copy(parms.soundShader, "", sizeof(parms.soundShader));
	else
		Str_Copy(parms.soundShader, level.configStrings[CS_SOUND_SHADERS + entity->soundShader], sizeof(parms.soundShader));

	parms.soundParms[0] = speaker->soundParms[0];
	parms.soundParms[1] = speaker->soundParms[1];
	parms.soundParms[2] = speaker->soundParms[2];
	parms.soundParms[3] = speaker->soundParms[3];
	parms.soundParms[4] = speaker->soundParms[4];
	parms.soundParms[5] = speaker->soundParms[5];
	parms.soundParms[6] = speaker->soundParms[6];
	parms.soundParms[7] = speaker->soundParms[7];

	SG_EditSpeakerParameters(entity->state.number, &parms);

	return true;
}

/*
 ==================
 SG_EditSpeakers_f
 ==================
*/
static void SG_EditSpeakers_f (){

	editorCallbacks_t	callbacks;

	// Launch the speaker editor
	callbacks.createWindow = SG_CreateSpeakerEditorWindow;
	callbacks.destroyWindow = SG_DestroySpeakerEditorWindow;
	callbacks.mouseEvent = SG_SpeakerEditorMouseEvent;

	if (!glqCommon->LaunchEditor("speaker", &callbacks))
		return;

	sg_speakerEditor.active = true;

	sg_speakerEditor.focusSpeaker = NULL;
	sg_speakerEditor.editSpeaker = NULL;

	// Finish setting up, but don't edit anything yet
	SG_EditSpeakerParameters(-1, NULL);
}

/*
 ==================
 SG_AddSpeaker_f
 ==================
*/
static void SG_AddSpeaker_f (){

	etEntity		*entity;
	speaker_t		*speaker;
	speakerParms_t	parms;
	etDict			dict;
	glqVec3			origin;

	if (!sg_speakerEditor.active){
		G_Printf("You must launch the speaker editor to use this command\n");
		return;
	}

	// Add a new speaker with default parameters in front of the view
	origin = level.localClient->GetViewOrigin() + level.localClient->GetViewAngles().ToForward() * 100.0f;
	origin.Snap();

	dict.SetString("className", "speaker");
	dict.SetVec3("origin", origin);
	dict.SetString("soundShader", "_default");

	entity = SG_SpawnEntity(dict);
	if (!entity){
		G_Printf("Couldn't spawn a new speaker\n");
		return;
	}

	// Edit the new speaker
	sg_speakerEditor.editSpeaker = entity;

	speaker = (speaker_t *)entity->state.buffer;

	Str_Copy(parms.name, entity->name, sizeof(parms.name));
	Str_Copy(parms.bind, "", sizeof(parms.bind));
	parms.bindOrientated = false;
	parms.origin = entity->currentOrigin;
	parms.direction = speaker->direction;
	parms.flags = entity->spawnFlags;
	parms.wait = entity->wait;
	parms.random = entity->random;
	parms.audibleRange = entity->audibleRange;

	if (entity->soundShader == 0)
		Str_Copy(parms.soundShader, "", sizeof(parms.soundShader));
	else
		Str_Copy(parms.soundShader, level.configStrings[CS_SOUND_SHADERS + entity->soundShader], sizeof(parms.soundShader));

	parms.soundParms[0] = speaker->soundParms[0];
	parms.soundParms[1] = speaker->soundParms[1];
	parms.soundParms[2] = speaker->soundParms[2];
	parms.soundParms[3] = speaker->soundParms[3];
	parms.soundParms[4] = speaker->soundParms[4];
	parms.soundParms[5] = speaker->soundParms[5];
	parms.soundParms[6] = speaker->soundParms[6];
	parms.soundParms[7] = speaker->soundParms[7];

	SG_EditSpeakerParameters(entity->state.number, &parms);
}

/*
 ==================
 SG_RefreshSpeakerEditor
 ==================
*/
void SG_RefreshSpeakerEditor (){

	etEntity	*visibleEntities[MAX_ENTITIES];
	int			numVisibleEntities;
	etEntity	*entity;
	speaker_t	*speaker;
	clipModel_t	*clipModel;
	trace_t		trace;
	char		string[MAX_STRING_LENGTH];
	glqMat3		axis;
	glqVec3		origin;
	glqVec3		forward, up;
	float		fraction = 1.0f;
	int			i;

	if (!sg_speakerEditor.active)
		return;			// Not active

	sg_speakerEditor.focusSpeaker = NULL;

	// Get the current view origin and up vector
	origin = level.localClient->GetViewOrigin();

	level.localClient->GetViewAngles().ToVectors(&forward, NULL, &up);

	// Find visible speakers
	numVisibleEntities = 0;

	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse || entity->state.type != ET_SPEAKER)
			continue;

		// Always add the speaker that is being edited
		if (entity == sg_speakerEditor.editSpeaker){
			visibleEntities[numVisibleEntities++] = entity;
			continue;
		}

		// Cull speakers by distance
		if (entity->GetOrigin().Distance(origin) > 1000.0f)
			continue;

		// Add it to the list
		visibleEntities[numVisibleEntities++] = entity;
	}

	if (!numVisibleEntities)
		return;		// Nothing more to do here

	// Find the closest speaker that has focus, if any
	clipModel = glqCollision->SetupBoxModel(CONTENT_SOLID, glqBounds(-5.0f, 5.0f));

	for (i = 0; i < numVisibleEntities; i++){
		entity = visibleEntities[i];

		glqCollision->Trace(origin, origin + forward * MAX_TRACE_DISTANCE, bounds_zero, MASK_SOLID, clipModel, entity->GetOrigin(), entity->GetAngles().ToMat3(), &trace);

		if (trace.fraction < fraction){
			fraction = trace.fraction;

			// This speaker has focus
			sg_speakerEditor.focusSpeaker = entity;
		}
	}

	// Draw all visible speakers
	for (i = 0; i < numVisibleEntities; i++){
		entity = visibleEntities[i];
		speaker = (speaker_t *)entity->state.buffer;

		origin = entity->GetOrigin();
		axis = entity->GetAngles().ToMat3();

		// Draw a box at the origin
		if (entity != sg_speakerEditor.focusSpeaker && entity != sg_speakerEditor.editSpeaker)
			glqRenderer->DebugBox(colorWhite, origin, axis, glqBounds(-5.0f, 5.0f), false, VIEW_MAIN);
		else {
			glqRenderer->DebugBox(colorRed, origin, axis, glqBounds(-5.0f, 5.0f), false, VIEW_MAIN);

			if (entity->IsBound())
				glqRenderer->DebugBox(colorRed, entity->currentOrigin, entity->currentAxis, glqBounds(-2.5f, 2.5f), false, VIEW_MAIN);
		}

		// If editing this speaker
		if (entity == sg_speakerEditor.editSpeaker){
			// If it has a direction, draw a cone
			if (!speaker->direction.IsCleared()){
				glqRenderer->DebugCone(colorRed, origin, axis * speaker->direction * 20.0f, 0.0f, 5.0f, false, VIEW_MAIN);

				if (entity->IsBound())
					glqRenderer->DebugCone(colorRed, entity->currentOrigin, entity->currentAxis * speaker->direction * 10.0f, 0.0f, 2.5f, false, VIEW_MAIN);
			}
		}

		// Draw the entity and sound shader names
		if (entity->soundShader == 0)
			Str_SPrintf(string, sizeof(string), "%s", entity->name);
		else
			Str_SPrintf(string, sizeof(string), "%s (%s)", entity->name, level.configStrings[CS_SOUND_SHADERS + entity->soundShader]);

		glqRenderer->DebugText(colorWhite, true, origin + up * 10.0f, 2.0f, 4.0f, string, false, VIEW_MAIN);
	}
}

/*
 ==================
 SG_CloseSpeakerEditor
 ==================
*/
void SG_CloseSpeakerEditor (){

	SG_SpeakerEditorCloseCallback();
}

/*
 ==================
 SG_InitSpeakerEditor
 ==================
*/
void SG_InitSpeakerEditor (){

	// Add commands
	glqCmdSystem->AddCommand("editSpeakers", SG_EditSpeakers_f, "Launches the integrated speaker editor", NULL);
	glqCmdSystem->AddCommand("addSpeaker", SG_AddSpeaker_f, "Adds a new speaker", NULL);
}

/*
 ==================
 SG_ShutdownSpeakerEditor
 ==================
*/
void SG_ShutdownSpeakerEditor (){

	// Remove commands
	glqCmdSystem->RemoveCommand("editSpeakers");
	glqCmdSystem->RemoveCommand("addSpeaker");

	// Close the speaker editor if active
	if (sg_speakerEditor.active)
		glqCommon->CloseEditor();

	glqMemory->Fill(&sg_speakerEditor, 0, sizeof(speakerEditor_t));
}


/*
 ==============================================================================

 PLATFORM SPECIFIC EDITOR FUNCTIONS

 ==============================================================================
*/

#ifdef _WIN32

#define SPEAKER_EDITOR_WINDOW_NAME		GAME_NAME " Speaker Editor"
#define SPEAKER_EDITOR_WINDOW_CLASS		GAME_NAME " Speaker Editor"
#define SPEAKER_EDITOR_WINDOW_STYLE		(WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)

static speakerParms_t		sg_speakerParms = {"speaker", "", false, glqVec3(0.0f), glqVec3(0.0f), 0, 0.0f, 0.0f, 2500.0f, "_default", 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f};

struct speakerParmsEditor_t {
	bool					initialized;
	bool					enabled;

	int						editIndex;
	speakerParms_t			editParms;

	// Window stuff
	HINSTANCE				hInstance;

	HWND					hWnd;
	HWND					hWndName;
	HWND					hWndNameValue;
	HWND					hWndBindTo;
	HWND					hWndBindToValue;
	HWND					hWndBindOrientated;
	HWND					hWndFrame1;
	HWND					hWndFrame2;
	HWND					hWndFrame3;
	HWND					hWndFrame4;
	HWND					hWndX;
	HWND					hWndY;
	HWND					hWndZ;
	HWND					hWndOrigin;
	HWND					hWndOriginXValue;
	HWND					hWndOriginXSpin;
	HWND					hWndOriginYValue;
	HWND					hWndOriginYSpin;
	HWND					hWndOriginZValue;
	HWND					hWndOriginZSpin;
	HWND					hWndDirection;
	HWND					hWndDirectionXValue;
	HWND					hWndDirectionXSpin;
	HWND					hWndDirectionYValue;
	HWND					hWndDirectionYSpin;
	HWND					hWndDirectionZValue;
	HWND					hWndDirectionZSpin;
	HWND					hWndVolumePitch;
	HWND					hWndVolumeValue;
	HWND					hWndVolumeSpin;
	HWND					hWndPitchValue;
	HWND					hWndPitchSpin;
	HWND					hWndDryFilter;
	HWND					hWndDryFilterGainValue;
	HWND					hWndDryFilterGainSpin;
	HWND					hWndDryFilterGainHFValue;
	HWND					hWndDryFilterGainHFSpin;
	HWND					hWndWetFilter;
	HWND					hWndWetFilterGainValue;
	HWND					hWndWetFilterGainSpin;
	HWND					hWndWetFilterGainHFValue;
	HWND					hWndWetFilterGainHFSpin;
	HWND					hWndDistance;
	HWND					hWndMinDistanceValue;
	HWND					hWndMinDistanceSpin;
	HWND					hWndMaxDistanceValue;
	HWND					hWndMaxDistanceSpin;
	HWND					hWndGlobal;
	HWND					hWndLooping;
	HWND					hWndTriggered;
	HWND					hWndActivator;
	HWND					hWndTrigger;
	HWND					hWndWait;
	HWND					hWndWaitValue;
	HWND					hWndWaitSpin;
	HWND					hWndRandom;
	HWND					hWndRandomValue;
	HWND					hWndRandomSpin;
	HWND					hWndAudibleRange;
	HWND					hWndAudibleRangeValue;
	HWND					hWndAudibleRangeSpin;
	HWND					hWndSoundShader;
	HWND					hWndSoundShaderValue;
	HWND					hWndApply;
	HWND					hWndReset;
	HWND					hWndDefaults;
	HWND					hWndRemove;
	HWND					hWndSave;

	HFONT					hFont;
};

static speakerParmsEditor_t	sg_speakerParmsEditor;


/*
 ==================
 SG_AddSpeakerSoundShader
 ==================
*/
static void SG_AddSpeakerSoundShader (const char *name){

	if (SendMessage(sg_speakerParmsEditor.hWndSoundShaderValue, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)name) != CB_ERR)
		return;

	SendMessage(sg_speakerParmsEditor.hWndSoundShaderValue, CB_ADDSTRING, 0, (LPARAM)name);
}

/*
 ==================
 SG_ApplySpeakerParameters
 ==================
*/
static void SG_ApplySpeakerParameters (){

	speakerParms_t	parms;
	char			string[64];
	int				index;

	if (!sg_speakerParmsEditor.enabled)
		return;

	// Read the controls
	if (GetWindowText(sg_speakerParmsEditor.hWndNameValue, string, sizeof(string)))
		Str_Copy(parms.name, SG_ValidateEntityName(string, "speaker", sg_speakerParmsEditor.editIndex), sizeof(parms.name));
	else
		Str_Copy(parms.name, SG_ValidateEntityName(NULL, "speaker", sg_speakerParmsEditor.editIndex), sizeof(parms.name));

	index = SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		Str_Copy(parms.bind, "", sizeof(parms.bind));
	else
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_GETLBTEXT, (WPARAM)index, (LPARAM)parms.bind);

	if (SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_GETCHECK, 0, 0) == BST_CHECKED)
		parms.bindOrientated = true;
	else
		parms.bindOrientated = false;

	if (GetWindowText(sg_speakerParmsEditor.hWndOriginXValue, string, sizeof(string)))
		parms.origin[0] = Str_ToFloat(string);
	else
		parms.origin[0] = sg_speakerParmsEditor.editParms.origin[0];

	if (GetWindowText(sg_speakerParmsEditor.hWndOriginYValue, string, sizeof(string)))
		parms.origin[1] = Str_ToFloat(string);
	else
		parms.origin[1] = sg_speakerParmsEditor.editParms.origin[1];

	if (GetWindowText(sg_speakerParmsEditor.hWndOriginZValue, string, sizeof(string)))
		parms.origin[2] = Str_ToFloat(string);
	else
		parms.origin[2] = sg_speakerParmsEditor.editParms.origin[2];

	if (GetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, string, sizeof(string)))
		parms.direction[0] = Str_ToFloat(string);
	else
		parms.direction[0] = sg_speakerParmsEditor.editParms.direction[0];

	if (GetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, string, sizeof(string)))
		parms.direction[1] = Str_ToFloat(string);
	else
		parms.direction[1] = sg_speakerParmsEditor.editParms.direction[1];

	if (GetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, string, sizeof(string)))
		parms.direction[2] = Str_ToFloat(string);
	else
		parms.direction[2] = sg_speakerParmsEditor.editParms.direction[2];

	if (GetWindowText(sg_speakerParmsEditor.hWndVolumeValue, string, sizeof(string)))
		parms.soundParms[0] = Str_ToFloat(string);
	else
		parms.soundParms[0] = sg_speakerParmsEditor.editParms.soundParms[0];

	if (GetWindowText(sg_speakerParmsEditor.hWndPitchValue, string, sizeof(string)))
		parms.soundParms[1] = Str_ToFloat(string);
	else
		parms.soundParms[1] = sg_speakerParmsEditor.editParms.soundParms[1];

	if (GetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, string, sizeof(string)))
		parms.soundParms[2] = Str_ToFloat(string);
	else
		parms.soundParms[2] = sg_speakerParmsEditor.editParms.soundParms[2];

	if (GetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, string, sizeof(string)))
		parms.soundParms[3] = Str_ToFloat(string);
	else
		parms.soundParms[3] = sg_speakerParmsEditor.editParms.soundParms[3];

	if (GetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, string, sizeof(string)))
		parms.soundParms[4] = Str_ToFloat(string);
	else
		parms.soundParms[4] = sg_speakerParmsEditor.editParms.soundParms[4];

	if (GetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, string, sizeof(string)))
		parms.soundParms[5] = Str_ToFloat(string);
	else
		parms.soundParms[5] = sg_speakerParmsEditor.editParms.soundParms[5];

	if (GetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, string, sizeof(string)))
		parms.soundParms[6] = Str_ToFloat(string);
	else
		parms.soundParms[6] = sg_speakerParmsEditor.editParms.soundParms[6];

	if (GetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, string, sizeof(string)))
		parms.soundParms[7] = Str_ToFloat(string);
	else
		parms.soundParms[7] = sg_speakerParmsEditor.editParms.soundParms[7];

	parms.flags = 0;

	if (SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_GETCHECK, 0, 0) == BST_CHECKED)
		parms.flags |= SPEAKER_GLOBAL;

	if (SendMessage(sg_speakerParmsEditor.hWndLooping, BM_GETCHECK, 0, 0) == BST_CHECKED)
		parms.flags |= SPEAKER_LOOPING;

	if (SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_GETCHECK, 0, 0) == BST_CHECKED)
		parms.flags |= SPEAKER_TRIGGERED;

	if (SendMessage(sg_speakerParmsEditor.hWndActivator, BM_GETCHECK, 0, 0) == BST_CHECKED)
		parms.flags |= SPEAKER_ACTIVATOR;

	if (GetWindowText(sg_speakerParmsEditor.hWndWaitValue, string, sizeof(string)))
		parms.wait = Str_ToFloat(string);
	else
		parms.wait = sg_speakerParmsEditor.editParms.wait;

	if (GetWindowText(sg_speakerParmsEditor.hWndRandomValue, string, sizeof(string)))
		parms.random = Str_ToFloat(string);
	else
		parms.random = sg_speakerParmsEditor.editParms.random;

	if (GetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, string, sizeof(string)))
		parms.audibleRange = Str_ToFloat(string);
	else
		parms.audibleRange = sg_speakerParmsEditor.editParms.audibleRange;

	if (!GetWindowText(sg_speakerParmsEditor.hWndSoundShaderValue, parms.soundShader, sizeof(parms.soundShader)))
		Str_Copy(parms.soundShader, "", sizeof(parms.soundShader));

	// Update the parameters
	SG_SpeakerEditorUpdateCallback(sg_speakerParmsEditor.editIndex, &parms);
}

/*
 ==================
 SG_ResetSpeakerParameters
 ==================
*/
static void SG_ResetSpeakerParameters (speakerParms_t *parms){

	etEntity	*master;
	int			index;

	if (!sg_speakerParmsEditor.enabled)
		return;

	// Update the controls
	SetWindowText(sg_speakerParmsEditor.hWndNameValue, parms->name);

	index = SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)parms->bind);
	if (index == CB_ERR)
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)0, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)index, 0);

	master = SG_FindEntity(parms->bind);

	if (parms->bindOrientated)
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndOriginXValue, Str_FromFloat(parms->origin[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginYValue, Str_FromFloat(parms->origin[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginZValue, Str_FromFloat(parms->origin[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, Str_FromFloat(parms->direction[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, Str_FromFloat(parms->direction[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, Str_FromFloat(parms->direction[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndVolumeValue, Str_FromFloat(parms->soundParms[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndPitchValue, Str_FromFloat(parms->soundParms[1], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, Str_FromFloat(parms->soundParms[2], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, Str_FromFloat(parms->soundParms[3], -1));

	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, Str_FromFloat(parms->soundParms[4], -1));
	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, Str_FromFloat(parms->soundParms[5], -1));

	SetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, Str_FromFloat(parms->soundParms[6], -1));
	SetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, Str_FromFloat(parms->soundParms[7], -1));

	if (parms->flags & SPEAKER_GLOBAL)
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_LOOPING)
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_TRIGGERED)
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_ACTIVATOR)
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndWaitValue, Str_FromFloat(parms->wait, -1));
	SetWindowText(sg_speakerParmsEditor.hWndRandomValue, Str_FromFloat(parms->random, -1));

	SetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, Str_FromFloat(parms->audibleRange, -1));

	SetWindowText(sg_speakerParmsEditor.hWndSoundShaderValue, parms->soundShader);

	// Enable or disable the controls
	EnableWindow(sg_speakerParmsEditor.hWndBindOrientated, (master != NULL));

	// Update the parameters
	SG_SpeakerEditorUpdateCallback(sg_speakerParmsEditor.editIndex, parms);
}

/*
 ==================
 SG_RemoveSpeakerParameters
 ==================
*/
static void SG_RemoveSpeakerParameters (){

	etEntity	*master;
	int			index;

	if (!sg_speakerParmsEditor.enabled)
		return;

	sg_speakerParmsEditor.enabled = false;

	// Update the controls
	SetWindowText(sg_speakerParmsEditor.hWndNameValue, sg_speakerParms.name);

	index = SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)sg_speakerParms.bind);
	if (index == CB_ERR)
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)0, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)index, 0);

	master = SG_FindEntity(sg_speakerParms.bind);

	if (sg_speakerParms.bindOrientated)
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndOriginXValue, Str_FromFloat(sg_speakerParms.origin[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginYValue, Str_FromFloat(sg_speakerParms.origin[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginZValue, Str_FromFloat(sg_speakerParms.origin[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, Str_FromFloat(sg_speakerParms.direction[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, Str_FromFloat(sg_speakerParms.direction[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, Str_FromFloat(sg_speakerParms.direction[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndVolumeValue, Str_FromFloat(sg_speakerParms.soundParms[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndPitchValue, Str_FromFloat(sg_speakerParms.soundParms[1], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, Str_FromFloat(sg_speakerParms.soundParms[2], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, Str_FromFloat(sg_speakerParms.soundParms[3], -1));

	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, Str_FromFloat(sg_speakerParms.soundParms[4], -1));
	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, Str_FromFloat(sg_speakerParms.soundParms[5], -1));

	SetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, Str_FromFloat(sg_speakerParms.soundParms[6], -1));
	SetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, Str_FromFloat(sg_speakerParms.soundParms[7], -1));

	if (sg_speakerParms.flags & SPEAKER_GLOBAL)
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (sg_speakerParms.flags & SPEAKER_LOOPING)
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (sg_speakerParms.flags & SPEAKER_TRIGGERED)
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (sg_speakerParms.flags & SPEAKER_ACTIVATOR)
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndWaitValue, Str_FromFloat(sg_speakerParms.wait, -1));
	SetWindowText(sg_speakerParmsEditor.hWndRandomValue, Str_FromFloat(sg_speakerParms.random, -1));

	SetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, Str_FromFloat(sg_speakerParms.audibleRange, -1));

	SetWindowText(sg_speakerParmsEditor.hWndSoundShaderValue, sg_speakerParms.soundShader);

	// Disable the controls
	EnableWindow(sg_speakerParmsEditor.hWndName, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndNameValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndBindTo, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndBindToValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndBindOrientated, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndFrame1, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndFrame2, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndFrame3, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndFrame4, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndX, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndY, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndZ, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOrigin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginXValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginXSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginYValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginYSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginZValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndOriginZSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirection, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionXValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionXSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionYValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionYSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionZValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionZSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndVolumePitch, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndVolumeValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndVolumeSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndPitchValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndPitchSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilter, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainHFValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainHFSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilter, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainHFValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainHFSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDistance, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndMinDistanceValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndMinDistanceSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndMaxDistanceValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndMaxDistanceSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndGlobal, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndLooping, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndTriggered, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndActivator, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndTrigger, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWait, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWaitValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndWaitSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndRandom, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndRandomValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndRandomSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRange, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRangeValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRangeSpin, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndSoundShader, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndSoundShaderValue, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndApply, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndReset, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndDefaults, FALSE);
	EnableWindow(sg_speakerParmsEditor.hWndRemove, FALSE);

	// Remove the parameters
	SG_SpeakerEditorRemoveCallback(sg_speakerParmsEditor.editIndex);
}

/*
 ==================
 SG_SpeakerEditorWindowProc
 ==================
*/
static LRESULT CALLBACK SG_SpeakerEditorWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	etEntity	*self, *master;
	char		string[64];
	int			result;

	switch (uMsg){
	case WM_CLOSE:
		SG_ApplySpeakerParameters();

		SG_SpeakerEditorCloseCallback();

		break;
	case WM_SYSCOMMAND:
		if (wParam == SC_KEYMENU)
			return 0;

		break;
	case WM_COMMAND:
		if (HIWORD(wParam) == EN_KILLFOCUS){
			if ((HWND)lParam == sg_speakerParmsEditor.hWndNameValue){
				if (!sg_speakerParmsEditor.enabled)
					break;

				GetWindowText(sg_speakerParmsEditor.hWndNameValue, string, sizeof(string));
				SetWindowText(sg_speakerParmsEditor.hWndNameValue, SG_ValidateEntityName(string, "speaker", sg_speakerParmsEditor.editIndex));

				break;
			}
		}

		if (HIWORD(wParam) == BN_CLICKED){
			if ((HWND)lParam == sg_speakerParmsEditor.hWndBindOrientated){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndGlobal){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndLooping){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndTriggered){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndActivator){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndTrigger){
				SG_ApplySpeakerParameters();

				self = &level.entities[sg_speakerParmsEditor.editIndex];

				if (level.localClient == NULL)
					self->Trigger(self, NULL, NULL);
				else
					self->Trigger(self, level.localClient->entity, level.localClient->entity);

				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndApply){
				SG_ApplySpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndReset){
				SG_ResetSpeakerParameters(&sg_speakerParmsEditor.editParms);
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndDefaults){
				SG_ResetSpeakerParameters(&sg_speakerParms);
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndRemove){
				SG_RemoveSpeakerParameters();
				break;
			}

			if ((HWND)lParam == sg_speakerParmsEditor.hWndSave){
				SG_ApplySpeakerParameters();

				SG_SpeakerEditorSaveCallback();

				break;
			}
		}

		if (HIWORD(wParam) == CBN_SELCHANGE){
			if ((HWND)lParam == sg_speakerParmsEditor.hWndBindToValue){
				result = SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_GETCURSEL, 0, 0);

				if (result == CB_ERR){
					SG_ApplySpeakerParameters();
					break;
				}

				SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_GETLBTEXT, (WPARAM)result, (LPARAM)string);

				master = SG_FindEntity(string);

				EnableWindow(sg_speakerParmsEditor.hWndBindOrientated, (master != NULL));

				SG_ApplySpeakerParameters();

				break;
			}
		}

		break;
	case WM_NOTIFY:
		if (((LPNMHDR)lParam)->code == UDN_DELTAPOS){
			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndOriginXSpin){
				GetWindowText(sg_speakerParmsEditor.hWndOriginXValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndOriginXValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndOriginXValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndOriginYSpin){
				GetWindowText(sg_speakerParmsEditor.hWndOriginYValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndOriginYValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndOriginYValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndOriginZSpin){
				GetWindowText(sg_speakerParmsEditor.hWndOriginZValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndOriginZValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndOriginZValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndDirectionXSpin){
				GetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndDirectionYSpin){
				GetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndDirectionZSpin){
				GetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, Str_FromFloat(Str_ToFloat(string) + 1.0f, -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, Str_FromFloat(Str_ToFloat(string) - 1.0f, -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndVolumeSpin){
				GetWindowText(sg_speakerParmsEditor.hWndVolumeValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndVolumeValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.01f, 0.0f, 1.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndVolumeValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.01f, 0.0f, 1.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndPitchSpin){
				GetWindowText(sg_speakerParmsEditor.hWndPitchValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndPitchValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.1f, 0.1f, 10.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndPitchValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.1f, 0.1f, 10.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndDryFilterGainSpin){
				GetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.01f, 0.0f, 1.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.01f, 0.0f, 1.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndDryFilterGainHFSpin){
				GetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.01f, 0.0f, 1.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.01f, 0.0f, 1.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndWetFilterGainSpin){
				GetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.01f, 0.0f, 1.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.01f, 0.0f, 1.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndWetFilterGainHFSpin){
				GetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) + 0.01f, 0.0f, 1.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, Str_FromFloat(glqMath::ClampFloat(Str_ToFloat(string) - 0.01f, 0.0f, 1.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndMinDistanceSpin){
				GetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, Str_FromFloat(Max(Str_ToFloat(string) + 1.0f, 0.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, Str_FromFloat(Max(Str_ToFloat(string) - 1.0f, 0.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndMaxDistanceSpin){
				GetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, Str_FromFloat(Max(Str_ToFloat(string) + 1.0f, 0.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, Str_FromFloat(Max(Str_ToFloat(string) - 1.0f, 0.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndWaitSpin){
				GetWindowText(sg_speakerParmsEditor.hWndWaitValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndWaitValue, Str_FromFloat(Max(Str_ToFloat(string) + 0.1f, 0.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndWaitValue, Str_FromFloat(Max(Str_ToFloat(string) - 0.1f, 0.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndRandomSpin){
				GetWindowText(sg_speakerParmsEditor.hWndRandomValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndRandomValue, Str_FromFloat(Max(Str_ToFloat(string) + 0.1f, 0.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndRandomValue, Str_FromFloat(Max(Str_ToFloat(string) - 0.1f, 0.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}

			if (((LPNMHDR)lParam)->hwndFrom == sg_speakerParmsEditor.hWndAudibleRangeSpin){
				GetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, string, sizeof(string));

				if (((LPNMUPDOWN)lParam)->iDelta < 0)
					SetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, Str_FromFloat(Max(Str_ToFloat(string) + 1.0f, 0.0f), -1));
				else
					SetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, Str_FromFloat(Max(Str_ToFloat(string) - 1.0f, 0.0f), -1));

				SG_ApplySpeakerParameters();

				break;
			}
		}

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*
 ==================
 SG_CreateSpeakerEditorWindow
 ==================
*/
static void *SG_CreateSpeakerEditorWindow (){

	INITCOMMONCONTROLSEX	initCommonControls;
	WNDCLASSEX				wndClass;
	RECT					rect;
	HDC						hDC;
	int						screenWidth, screenHeight;
	int						x, y, w, h;
	int						size;
	int						i;

	// Get the instance handle
	sg_speakerParmsEditor.hInstance = (HINSTANCE)glqSystem->GetInstanceHandle();

	// Initialize up-down control class
	initCommonControls.dwSize = sizeof(INITCOMMONCONTROLSEX);
	initCommonControls.dwICC = ICC_UPDOWN_CLASS;

	InitCommonControlsEx(&initCommonControls);

	// Calculate window position and dimensions
	screenWidth = GetSystemMetrics(SM_CXSCREEN);
	screenHeight = GetSystemMetrics(SM_CYSCREEN);
	
	rect.left = (screenWidth - 566) / 2;
	rect.top = (screenHeight - 394) / 2;
	rect.right = rect.left + 566;
	rect.bottom = rect.top + 394;

	AdjustWindowRectEx(&rect, SPEAKER_EDITOR_WINDOW_STYLE, FALSE, 0);
	
	x = rect.left;
	y = rect.top;
	w = rect.right - rect.left;
	h = rect.bottom - rect.top;

	// Register the window class
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = 0;
	wndClass.lpfnWndProc = SG_SpeakerEditorWindowProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = (HINSTANCE)glqSystem->GetInstanceHandle();
	wndClass.hIcon = (HICON)glqSystem->GetIconHandle();
	wndClass.hIconSm = NULL;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = SPEAKER_EDITOR_WINDOW_CLASS;

	if (!RegisterClassEx(&wndClass)){
		G_Printf(S_COLOR_RED "Could not register speaker editor window class\n");

		return NULL;
	}

	// Create the window
	sg_speakerParmsEditor.hWnd = CreateWindowEx(0, SPEAKER_EDITOR_WINDOW_CLASS, SPEAKER_EDITOR_WINDOW_NAME, SPEAKER_EDITOR_WINDOW_STYLE, x, y, w, h, NULL, NULL, sg_speakerParmsEditor.hInstance, NULL);
	if (!sg_speakerParmsEditor.hWnd){
		UnregisterClass(SPEAKER_EDITOR_WINDOW_CLASS, sg_speakerParmsEditor.hInstance);

		G_Printf(S_COLOR_RED "Could not create speaker editor window\n");

		return NULL;
	}

	// Create the controls
	sg_speakerParmsEditor.hWndName = CreateWindowEx(0, "STATIC", "Name", WS_CHILD | WS_VISIBLE | SS_LEFT, 8, 12, 180, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndNameValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL, 8, 26, 180, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndBindTo = CreateWindowEx(0, "STATIC", "Bind to", WS_CHILD | WS_VISIBLE | SS_RIGHT, 314, 16, 60, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndBindToValue = CreateWindowEx(WS_EX_CLIENTEDGE, "COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWNLIST | CBS_SORT, 378, 12, 180, 200, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndBindOrientated = CreateWindowEx(0, "BUTTON", "Bind orientated", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 378, 60, 180, 16, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndFrame1 = CreateWindowEx(0, "BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 8, 84, 310, 70, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndFrame2 = CreateWindowEx(0, "BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 8, 160, 310, 138, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndFrame3 = CreateWindowEx(0, "BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 326, 84, 232, 214, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndFrame4 = CreateWindowEx(0, "BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 8, 304, 550, 36, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndX = CreateWindowEx(0, "STATIC", "X", WS_CHILD | WS_VISIBLE | SS_CENTER, 66, 92, 80, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndY = CreateWindowEx(0, "STATIC", "Y", WS_CHILD | WS_VISIBLE | SS_CENTER, 150, 92, 80, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndZ = CreateWindowEx(0, "STATIC", "Z", WS_CHILD | WS_VISIBLE | SS_CENTER, 234, 92, 80, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOrigin = CreateWindowEx(0, "STATIC", "Origin", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 109, 50, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginXValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 66, 106, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginXSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 126, 106, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginYValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 106, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginYSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 106, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginZValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 106, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndOriginZSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 106, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirection = CreateWindowEx(0, "STATIC", "Direction", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 133, 50, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionXValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 66, 130, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionXSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 126, 130, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionYValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 130, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionYSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 130, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionZValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 130, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDirectionZSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 130, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndVolumePitch = CreateWindowEx(0, "STATIC", "Volume / Pitch", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 173, 134, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndVolumeValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 170, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndVolumeSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 170, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndPitchValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 170, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndPitchSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 170, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDryFilter = CreateWindowEx(0, "STATIC", "Dry filter (direct path)", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 213, 134, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDryFilterGainValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 210, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDryFilterGainSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 210, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDryFilterGainHFValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 210, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDryFilterGainHFSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 210, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWetFilter = CreateWindowEx(0, "STATIC", "Wet filter (reverb path)", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 237, 134, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWetFilterGainValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 234, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWetFilterGainSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 234, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWetFilterGainHFValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 234, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWetFilterGainHFSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 234, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDistance = CreateWindowEx(0, "STATIC", "Distance (min / max)", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 277, 134, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndMinDistanceValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 150, 274, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndMinDistanceSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 210, 274, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndMaxDistanceValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 234, 274, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndMaxDistanceSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 294, 274, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndGlobal = CreateWindowEx(0, "BUTTON", "Global", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 330, 94, 140, 16, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndLooping = CreateWindowEx(0, "BUTTON", "Looping", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 330, 114, 140, 16, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndTriggered = CreateWindowEx(0, "BUTTON", "Triggered", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 330, 134, 140, 16, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndActivator = CreateWindowEx(0, "BUTTON", "Activator", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX, 330, 154, 140, 16, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndTrigger = CreateWindowEx(0, "BUTTON", "Trigger", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 474, 114, 80, 35, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWait = CreateWindowEx(0, "STATIC", "Wait time", WS_CHILD | WS_VISIBLE | SS_RIGHT, 330, 205, 140, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWaitValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 474, 202, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndWaitSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 534, 202, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndRandom = CreateWindowEx(0, "STATIC", "Wait variance", WS_CHILD | WS_VISIBLE | SS_RIGHT, 330, 229, 140, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndRandomValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 474, 226, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndRandomSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 534, 226, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndAudibleRange = CreateWindowEx(0, "STATIC", "Audible range", WS_CHILD | WS_VISIBLE | SS_RIGHT, 330, 277, 140, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndAudibleRangeValue = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_RIGHT | ES_AUTOHSCROLL, 474, 274, 80, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndAudibleRangeSpin = CreateWindowEx(0, UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | WS_TABSTOP | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY, 534, 274, 20, 20, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndSoundShader = CreateWindowEx(0, "STATIC", "Sound Shader", WS_CHILD | WS_VISIBLE | SS_RIGHT, 12, 318, 134, 14, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndSoundShaderValue = CreateWindowEx(WS_EX_CLIENTEDGE, "COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | CBS_DROPDOWN | CBS_SORT | CBS_AUTOHSCROLL, 150, 314, 404, 200, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndApply = CreateWindowEx(0, "BUTTON", "Apply", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 8, 363, 75, 23, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndReset = CreateWindowEx(0, "BUTTON", "Reset", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 87, 363, 75, 23, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndDefaults = CreateWindowEx(0, "BUTTON", "Defaults", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 166, 363, 75, 23, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndRemove = CreateWindowEx(0, "BUTTON", "Remove", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 245, 363, 75, 23, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);
	sg_speakerParmsEditor.hWndSave = CreateWindowEx(0, "BUTTON", "Save", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, 483, 363, 75, 23, sg_speakerParmsEditor.hWnd, NULL, sg_speakerParmsEditor.hInstance, NULL);

	// Create and set the font
	hDC = GetDC(sg_speakerParmsEditor.hWnd);
	size = -MulDiv(8, GetDeviceCaps(hDC, LOGPIXELSY), 72);
	ReleaseDC(sg_speakerParmsEditor.hWnd, hDC);

	sg_speakerParmsEditor.hFont = CreateFont(size, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "Microsoft Sans Serif");

	SendMessage(sg_speakerParmsEditor.hWndName, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndNameValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndBindTo, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndBindToValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndBindOrientated, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndFrame1, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndFrame2, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndFrame3, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndFrame4, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndX, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndY, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndZ, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndOrigin, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndOriginXValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndOriginYValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndOriginZValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDirection, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDirectionXValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDirectionYValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDirectionZValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndVolumePitch, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndVolumeValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndPitchValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDryFilter, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDryFilterGainValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDryFilterGainHFValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndWetFilter, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndWetFilterGainValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndWetFilterGainHFValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDistance, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndMinDistanceValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndMaxDistanceValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndGlobal, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndLooping, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndTriggered, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndActivator, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndTrigger, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndWait, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndWaitValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndRandom, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndRandomValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndAudibleRange, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndAudibleRangeValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndSoundShader, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndSoundShaderValue, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndApply, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndReset, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndDefaults, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndRemove, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);
	SendMessage(sg_speakerParmsEditor.hWndSave, WM_SETFONT, (WPARAM)sg_speakerParmsEditor.hFont, FALSE);

	// Fill bind-to combo box
	SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_ADDSTRING, 0, (LPARAM)"");

	for (i = MAX_CLIENTS; i < level.numEntities; i++){
		if (!level.entities[i].inUse)
			continue;

		if (level.entities[i].state.type == ET_SPEAKER)
			continue;

		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_ADDSTRING, 0, (LPARAM)level.entities[i].name);
	}

	// Fill sound shader combo box
	SendMessage(sg_speakerParmsEditor.hWndSoundShaderValue, CB_ADDSTRING, 0, (LPARAM)"_default");

	glqDefManager->EnumDefs(DEF_SOUNDSHADER, SG_AddSpeakerSoundShader);

	// Set text limit for name edit box
	SendMessage(sg_speakerParmsEditor.hWndNameValue, CB_LIMITTEXT, (WPARAM)(MAX_ENTITY_NAME_LENGTH - 1), 0);

	// Set text limit for sound shader combo box
	SendMessage(sg_speakerParmsEditor.hWndSoundShaderValue, CB_LIMITTEXT, (WPARAM)(MAX_PATH_LENGTH - 1), 0);

	// Show the window
	ShowWindow(sg_speakerParmsEditor.hWnd, SW_SHOW);
	UpdateWindow(sg_speakerParmsEditor.hWnd);
	SetForegroundWindow(sg_speakerParmsEditor.hWnd);
	SetFocus(sg_speakerParmsEditor.hWnd);

	sg_speakerParmsEditor.initialized = true;

	return sg_speakerParmsEditor.hWnd;
}

/*
 ==================
 SG_DestroySpeakerEditorWindow
 ==================
*/
static void SG_DestroySpeakerEditorWindow (){

	if (!sg_speakerParmsEditor.initialized)
		return;

	if (sg_speakerParmsEditor.hFont)
		DeleteObject(sg_speakerParmsEditor.hFont);

	ShowWindow(sg_speakerParmsEditor.hWnd, SW_HIDE);
	DestroyWindow(sg_speakerParmsEditor.hWnd);

	UnregisterClass(SPEAKER_EDITOR_WINDOW_CLASS, sg_speakerParmsEditor.hInstance);

	glqMemory->Fill(&sg_speakerParmsEditor, 0, sizeof(speakerParmsEditor_t));
}

/*
 ==================
 SG_EditSpeakerParameters
 ==================
*/
static void SG_EditSpeakerParameters (int index, speakerParms_t *parms){

	etEntity	*master;

	if (!sg_speakerParmsEditor.initialized)
		return;

	// Apply current speaker parameters
	SG_ApplySpeakerParameters();

	// Set the current speaker parameters
	if (parms){
		sg_speakerParmsEditor.enabled = true;

		sg_speakerParmsEditor.editIndex = index;
		sg_speakerParmsEditor.editParms = *parms;
	}
	else {
		sg_speakerParmsEditor.enabled = false;

		parms = &sg_speakerParms;
	}

	// Update the controls
	SetWindowText(sg_speakerParmsEditor.hWndNameValue, parms->name);

	index = SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)parms->bind);
	if (index == CB_ERR)
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)0, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindToValue, CB_SETCURSEL, (WPARAM)index, 0);

	master = SG_FindEntity(parms->bind);

	if (parms->bindOrientated)
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndBindOrientated, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndOriginXValue, Str_FromFloat(parms->origin[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginYValue, Str_FromFloat(parms->origin[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndOriginZValue, Str_FromFloat(parms->origin[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDirectionXValue, Str_FromFloat(parms->direction[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionYValue, Str_FromFloat(parms->direction[1], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDirectionZValue, Str_FromFloat(parms->direction[2], -1));

	SetWindowText(sg_speakerParmsEditor.hWndVolumeValue, Str_FromFloat(parms->soundParms[0], -1));
	SetWindowText(sg_speakerParmsEditor.hWndPitchValue, Str_FromFloat(parms->soundParms[1], -1));

	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainValue, Str_FromFloat(parms->soundParms[2], -1));
	SetWindowText(sg_speakerParmsEditor.hWndDryFilterGainHFValue, Str_FromFloat(parms->soundParms[3], -1));

	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainValue, Str_FromFloat(parms->soundParms[4], -1));
	SetWindowText(sg_speakerParmsEditor.hWndWetFilterGainHFValue, Str_FromFloat(parms->soundParms[5], -1));

	SetWindowText(sg_speakerParmsEditor.hWndMinDistanceValue, Str_FromFloat(parms->soundParms[6], -1));
	SetWindowText(sg_speakerParmsEditor.hWndMaxDistanceValue, Str_FromFloat(parms->soundParms[7], -1));

	if (parms->flags & SPEAKER_GLOBAL)
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndGlobal, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_LOOPING)
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndLooping, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_TRIGGERED)
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndTriggered, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	if (parms->flags & SPEAKER_ACTIVATOR)
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_CHECKED, 0);
	else
		SendMessage(sg_speakerParmsEditor.hWndActivator, BM_SETCHECK, (WPARAM)BST_UNCHECKED, 0);

	SetWindowText(sg_speakerParmsEditor.hWndWaitValue, Str_FromFloat(parms->wait, -1));
	SetWindowText(sg_speakerParmsEditor.hWndRandomValue, Str_FromFloat(parms->random, -1));

	SetWindowText(sg_speakerParmsEditor.hWndAudibleRangeValue, Str_FromFloat(parms->audibleRange, -1));

	SetWindowText(sg_speakerParmsEditor.hWndSoundShaderValue, parms->soundShader);

	// Enable or disable the controls
	EnableWindow(sg_speakerParmsEditor.hWndName, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndNameValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndBindTo, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndBindToValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndBindOrientated, sg_speakerParmsEditor.enabled && (master != NULL));
	EnableWindow(sg_speakerParmsEditor.hWndFrame1, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndFrame2, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndFrame3, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndFrame4, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndX, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndY, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndZ, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOrigin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginXValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginXSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginYValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginYSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginZValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndOriginZSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirection, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionXValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionXSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionYValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionYSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionZValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDirectionZSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndVolumePitch, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndVolumeValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndVolumeSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndPitchValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndPitchSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilter, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainHFValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDryFilterGainHFSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilter, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainHFValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWetFilterGainHFSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDistance, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndMinDistanceValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndMinDistanceSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndMaxDistanceValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndMaxDistanceSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndGlobal, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndLooping, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndTriggered, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndActivator, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndTrigger, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWait, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWaitValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndWaitSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndRandom, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndRandomValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndRandomSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRange, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRangeValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndAudibleRangeSpin, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndSoundShader, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndSoundShaderValue, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndApply, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndReset, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndDefaults, sg_speakerParmsEditor.enabled);
	EnableWindow(sg_speakerParmsEditor.hWndRemove, sg_speakerParmsEditor.enabled);

	// Show the window
	ShowWindow(sg_speakerParmsEditor.hWnd, SW_RESTORE);
	UpdateWindow(sg_speakerParmsEditor.hWnd);
	SetForegroundWindow(sg_speakerParmsEditor.hWnd);
	SetFocus(sg_speakerParmsEditor.hWnd);
}

#else

/*
 ==================
 SG_CreateSpeakerEditorWindow
 ==================
*/
static void *SG_CreateSpeakerEditorWindow (){

	G_Printf(S_COLOR_RED "The speaker editor is not currently supported on this platform");

	return NULL;
}

/*
 ==================
 SG_DestroySpeakerEditorWindow
 ==================
*/
static void SG_DestroySpeakerEditorWindow (){

}

/*
 ==================
 SG_EditSpeakerParameters
 ==================
*/
static void SG_EditSpeakerParameters (int index, speakerParms_t *parms){

}

#endif